import subprocess
import threading
import asyncio
import websockets
import json
import base64
import wx
import os
import psutil
import time
import requests
import shutil
import sys

import call
import config



_cmdclass = { }



class while_loop(threading.Thread):
    def __init__(self, window):
        super(while_loop, self).__init__()
        self.daemon = True
        self.window = window
        self.loops = True

    def run(self):
        v = 0
        while self.loops:
            try:
                if v > 60:
                    for key, value in _cmdclass.items():
                        if value["object"].venv.poll() is None:
                            pass
                        else: # 重新启动
                            _cmd = CmdClass(self.window, key, value.get("args", []))
                            _cmd.start()
                            value["object"] = _cmd
                            _cmdclass.update({key : value})
                            wx.CallAfter(self.window.mesage_add, " [ " + key + " ] 掉线重启")
                    v = 0
                else:
                    v += 1
                    time.sleep(1)
            except Exception as e:
                wx.CallAfter(self.window.mesage_add, "while_loop: " + str(type(e)) + " " + str(e))

        for _, value in _cmdclass.items():
            value["object"].stop() 

    def stop(self):
        self.loops = False



class pyqt_thread(threading.Thread): # 异步任务类
    def __init__(self, window):
        super(pyqt_thread, self).__init__()
        self.daemon = True
        self.window = window
        self.loop = asyncio.new_event_loop()

    def run(self):
        self.loop.run_forever()

    def _stop(self):
        self.loop.call_soon_threadsafe(self.loop.stop)
        while self.loop.is_running():
            time.sleep(0.01)
        self.loop.call_soon_threadsafe(self.loop.close)

    def my_call(self, call, *kavy):
        asyncio.run_coroutine_threadsafe(call(self.window, *kavy), self.loop)  



async def async_localhost_model(window, self, json_data):
    localhost_model(self, json_data)



# 本地对接操作
def localhost_model(self, json_data):
    try:
        if json_data["type"] == "log":
            wx.CallAfter(self.window.mesage_add, "{}".format(json_data["mesg"]))

        elif json_data["type"] == "getuser":
            _dict = {
                "type": "userdata",
                "data": {
                    "userid": self.window.cli._dict.get("wuserid"),
                    "bind": self.window.cli._dict.get("bind"),
                    "data": self.window.cli._dict.get("data"),
                    "ipaddr": self.window.cli._dict.get("ipaddr")
                }
            }
            self.packput(_dict)

        elif json_data["type"] == "startws": # 启动ws链接
            if not self.window.mws:
                wx.CallAfter(self.window.on_StartServer, None)
            elif self.window.mws and self.window.mws.websocket.closed: # 如果有对象且链接关闭了就启动
                wx.CallAfter(self.window.on_StartServer, None)
            wx.CallAfter(self.window.mesage_add, "启动链接")

        elif json_data["type"] == "closews": # 关闭ws链接
            if self.window.mws: # 对象存在再关闭
                wx.CallAfter(self.window.on_CloseServer, None)
            wx.CallAfter(self.window.mesage_add, "关闭链接")

        elif json_data["type"] == "restartws":
            wx.CallAfter(self.window.mesage_add, "重启链接")
            if self.window.mws: # 对象存在再关闭
                wx.CallAfter(self.window.on_CloseServer, None)
                time.sleep(1)
            wx.CallAfter(self.window.on_StartServer, None)

        elif json_data["type"] == "init_data": # 表初始化
            wx.CallAfter(self.window.on_BindInit, None, 2)
            wx.CallAfter(self.window.mesage_add, "初始化")

        elif json_data["type"] == "init_bind": # 数据绑定
            wx.CallAfter(self.window.on_BindInit, None, 1)
            wx.CallAfter(self.window.mesage_add, "自动绑定")

        elif json_data["type"] == "app_status": # 子挂载程序操作
            listcmd = self.window.cli._dict.get("listcmd", [])
            json_mesg = json_data["mesg"]
            
            _model = json_mesg["model"]
            if _model == "append":
                dic = {"url": json_mesg.get("url", ""), "status": True}
                _plugin_path = os.path.join(os.path.dirname(config.START_EXE_PATH), "_plugin")
                if not os.path.exists(_plugin_path):
                    os.mkdir(_plugin_path)
                _, file_extension = os.path.splitext(dic["url"])
                dic["path"] = os.path.join(_plugin_path, dic["url"].split("=")[-1]) if file_extension == ".exe" else os.path.join(_plugin_path, dic["url"].split("=")[-1].replace(file_extension, ""), "main.exe")
                if dic["path"] in [ key["path"] for key in listcmd ]:
                    wx.CallAfter(self.window.mesage_add, "已存在挂载 [ {} ]".format(dic["path"]))
                else:
                    if not os.path.exists(dic["path"]):
                        r = requests.get(dic["url"])
                        if r.status_code == 200:
                            call.update_exe_write(self.window, file_extension, dic["path"], r)
                    listcmd.append(dic)
                    self.window.cli.setIni("listcmd", listcmd)
                    if self.window.gzframe:
                        wx.CallAfter(self.window.gzframe.add_model, dic)
                    wx.CallAfter(self.window.mesage_add, "添加挂载程序 {}".format(dic["path"]))
                    return

            _path = json_mesg.get("path")
            if not _path:
                _index = json_mesg.get("index", len(listcmd) + 1)
                if len(listcmd) > int(_index):
                    dic = listcmd[int(_index)]
                else:
                    wx.CallAfter(self.window.mesage_add, "index 不存在[超标]")
                    return
            else:
                if _path in _cmdclass:
                    dic = {"path": _cmdclass[_path]["path"], "url": _cmdclass[_path]["url"], "status": _cmdclass[_path]["status"]}
                else:
                    wx.CallAfter(self.window.mesage_add, "path 不存在[不在运行中的任务]")
                    return

            objects = _cmdclass.get(dic["path"])
            if _model == "start":
                if not objects:
                    _cmd = CmdClass(self.window, dic["path"], dic.get("args", []))
                    _cmd.start()
                    _cmdclass.update({dic["path"]: {"object": _cmd, "path": dic["path"], "url": dic["url"], "status": dic["status"], "args": dic.get("args", [])}})
                if self.window.gzframe:
                    wx.CallAfter(self.window.gzframe.Enable_start, dic["path"])
                wx.CallAfter(self.window.mesage_add, "启动挂载程序 {}".format(dic["path"]))
            elif _model == "stop":
                if objects:
                    objects["object"].stop()
                    _cmdclass.pop(dic["path"])
                if self.window.gzframe:
                    wx.CallAfter(self.window.gzframe.Enable_stop, dic["path"])
                wx.CallAfter(self.window.mesage_add, "关闭挂载程序 {}".format(dic["path"]))
            elif _model == "install":
                if objects:
                    objects["object"].stop()
                    _cmdclass.pop(dic["path"])
                if self.window.gzframe:
                    wx.CallAfter(self.window.gzframe.Enable_stop, dic["path"])
                wx.CallAfter(self.window.mesage_add, "更新挂载程序 {}".format(dic["path"]))
                r = requests.get(dic["url"])
                if r.status_code == 200:
                    _, file_extension = os.path.splitext(dic["url"])
                    call.update_exe_write(self.window, file_extension, dic["path"], r)
                wx.CallAfter(self.window.mesage_add, "更新完毕 {}".format(dic["path"]))
            elif _model == "restart":
                if objects:
                    objects["object"].stop()
                _cmd = CmdClass(self.window, dic["path"], dic.get("args", []))
                _cmd.start()
                _cmdclass.update({dic["path"]: {"object": _cmd, "path": dic["path"], "url": dic["url"], "status": dic["status"], "args": dic.get("args", [])}})
                if self.window.gzframe:
                    wx.CallAfter(self.window.gzframe.Enable_start, dic["path"])
                wx.CallAfter(self.window.mesage_add, "重启挂载程序 {}".format(dic["path"]))
            elif _model == "delete":
                for val in listcmd[:]:
                    if val["path"] == dic["path"]:
                        listcmd.remove(val)
                        wx.CallAfter(self.window.mesage_add, "删除挂载程序 {}".format(dic["path"]))
                        break
                self.window.cli.setIni("listcmd", listcmd)
                if objects:
                    objects["object"].stop()
                    _cmdclass.pop(dic["path"])
                if self.window.gzframe:
                    wx.CallAfter(self.window.gzframe.del_model, dic["path"])
                _plugin_path = os.path.join(os.path.dirname(config.START_EXE_PATH), "_plugin")
                if _plugin_path in dic["path"] and os.path.exists(dic["path"]):
                    new_path =  os.path.dirname(dic["path"]) if os.path.dirname(dic["path"]) != _plugin_path else dic["path"]
                    shutil.rmtree(new_path)

        elif json_data["type"] == "get_data":
            if self.window.mws:
                mesg = self.window.cli._dict
                _listcmd = mesg["listcmd"]
                for key in _listcmd:
                    if key["path"] in _cmdclass:
                        key.update({"isrunning":True})
                    else:
                        key.update({"isrunning":False})
                mesg["listcmd"] = _listcmd
                _dict = {
                    "type": "broadcast",
                    "code": 2,
                    "data": {
                        "code": 10001,
                        "type": "userdata",
                        "mesg": mesg,
                        "head": { "_id": "{}".format(json_data["mesg"]), "type": "info" }
                    }
                }
                self.window.mws.send(json.dumps(_dict).encode('utf-8'))

        elif json_data["type"] == "set_ipaddr": # 修改服务器地址
            _ipaddr = "{}".format(json_data["mesg"])
            wx.CallAfter(self.window.mesage_add, "服务器地址值变化 {} => {}".format(self.window.ipaddr_text.GetValue(), _ipaddr))
            wx.CallAfter(self.window.ipaddr_text.SetValue, _ipaddr)
            wx.CallAfter(self.window.Layout)
            self.window.cli.setIni("ipaddr", _ipaddr)
            
        elif json_data["type"] == "set_wbookid": # 修改表名ID
            _wbookid = "{}".format(json_data["mesg"])
            wx.CallAfter(self.window.mesage_add, "表名ID值变化 {} => {}".format(self.window.nameid_text.GetValue(), _wbookid))
            wx.CallAfter(self.window.nameid_text.SetValue, _wbookid)
            self.window.cli.setIni("wbookid", _wbookid)
            wx.CallAfter(self.window.Layout)

        elif json_data["type"] == "set_wuserid": # 修改绑定ID
            _wuserid = "{}".format(json_data["mesg"])
            wx.CallAfter(self.window.mesage_add, "数据ID值变化 {} => {}".format(self.window.userid_text.GetValue(), _wuserid))
            wx.CallAfter(self.window.userid_text.SetValue, _wuserid)
            self.window.cli.setIni("wuserid", _wuserid) 
            wx.CallAfter(self.window.Layout)

        elif json_data["type"] == "set_statrun": # 修改开机自启
            _statrun = json_data["mesg"]
            wx.CallAfter(self.window.mesage_add, "开机自启值变化 {} => {}".format(self.window.startk_box.GetValue(), _statrun))
            wx.CallAfter(self.window.startk_box.SetValue, _statrun)
            self.window.cli.setIni("statrun", _statrun) 
            wx.CallAfter(self.window.Layout)
            wx.CallAfter(self.window.on_StarRun, None, 1)

        elif json_data["type"] == "set_statcmd": # 修改数据转发
            _statcmd = json_data["mesg"]
            wx.CallAfter(self.window.mesage_add, "数据转发值变化 {} => {}".format(self.window.startk_call.GetValue(), _statcmd))
            wx.CallAfter(self.window.startk_call.SetValue, _statcmd)
            self.window.statcmd = _statcmd
            self.window.cli.setIni("statcmd", _statcmd) 
            wx.CallAfter(self.window.Layout)

        elif json_data["type"] == "set_bindid":
            self.window.cli.setIni("bindid", json_data["mesg"]["bindid"]) 

        elif json_data["type"] == "set_data":
            json_mesg = json_data["mesg"]
            _bind = json_mesg.get("bind")
            if _bind:
                self.window.cli.setIni("bind", _bind)
            _data = json_mesg.get("data")
            if _data:
                self.window.cli.setIni("data", _data)
            _listcmd = json_mesg.get("listcmd")
            if _listcmd:
                self.window.cli.setIni("listcmd", _listcmd)

    except Exception as e:
        wx.CallAfter(self.window.mesage_add, "localhost_send_data: " + str(type(e)) + " " + str(e))



class CmdClass(threading.Thread): # 挂载程序对接类
    def __init__(self, window, cmd_path, args):
        super(CmdClass, self).__init__()
        self.cmd_path = cmd_path
        self.daemon = True
        self.venv = subprocess.Popen([self.cmd_path] + args, 
            stdin=subprocess.PIPE, 
            stdout=subprocess.PIPE, 
            stderr=subprocess.PIPE,
            shell=True,
            creationflags=subprocess.CREATE_NEW_PROCESS_GROUP,
        )
        self.window = window

    def run(self):
        while True:
            try:
                line = self.venv.stdout.readline()
                if not line:
                    break
                ret_data = line.decode('utf-8').strip()
                json_data = json.loads(base64.b64decode(ret_data))
                if json_data["type"] == "broadcast" or json_data["type"] == "retransmission":
                    self.window.mws.send(json.dumps(json_data).encode('utf-8'))
                elif json_data["type"] == "localhost":
                    localhost_model(self, json_data["data"])
            except json.JSONDecodeError:
                wx.CallAfter(self.window.mesage_add, "json解析错误： " + ret_data)
            except UnicodeDecodeError as e:
                wx.CallAfter(self.window.mesage_add, "编码异常： " + str(type(e)) + str(e))
            except ValueError:
                break
            except Exception as e:
                wx.CallAfter(self.window.mesage_add, "读取错误： " + str(type(e)) + str(e))
                break 
        self.close()  

    def put(self, commd):
        commd = commd + "\n"
        self.venv.stdin.write(commd.encode("utf-8"))
        self.venv.stdin.flush()

    def packput(self, commd):
        self.put(base64.b64encode(json.dumps(commd).encode('utf-8')).decode('utf-8'))

    def stop(self):
        try:
            proc = subprocess.Popen(
                ["taskkill", "/f", "/t", "/pid", "{}".format(self.venv.pid)], 
                stdout=subprocess.PIPE, 
                stderr=subprocess.PIPE, 
                stdin=subprocess.PIPE,
                shell=True)
            proc.wait()
            time.sleep(0.5)

            subprocess.Popen(
                [os.path.join(getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__))), "TrayIconBuster.exe")], 
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                shell=True)
        except Exception as e:
            wx.CallAfter(self.window.mesage_add, str(type(e)) + str(e))

    def close(self):
        try:
            self.packput({"type":"STOP"})
            self.venv.stdin.close()
            self.venv.stdout.close()
            self.venv.terminate()
        except (Exception, psutil.NoSuchProcess) as e:
            wx.CallAfter(self.window.mesage_add, str(type(e)) + str(e))   



class Wsclient(threading.Thread): # gin-element 对接类
    def __init__(self, window):
        super(Wsclient, self).__init__()
        self.daemon = True
        self.window = window
        self.ipaddr = self.window.ipaddr_text.GetValue()
        self.userid = self.window.userid_text.GetValue()
        self.bookid = self.window.nameid_text.GetValue()
        self.loop = asyncio.new_event_loop()
        self.websocket = None

    def run(self):
        self.run_websockets()
        self.loop.run_forever()

    def run_websockets(self):
        asyncio.run_coroutine_threadsafe(self.connect(), self.loop)  

    def close(self):
        asyncio.run_coroutine_threadsafe(self._close(), self.loop)

    def send(self, mesg):
        asyncio.run_coroutine_threadsafe(self.websocket.send(mesg), self.loop)

    def _stop(self):
        self.loop.call_soon_threadsafe(self.loop.stop)
        while self.loop.is_running():
            time.sleep(0.01)
        self.loop.call_soon_threadsafe(self.loop.close)

    async def _close(self):
        wx.CallAfter(self.window.m_button_stop.SetLabel, "关闭中")
        if self.websocket is not None:
            await self.websocket.close()
            await self.websocket.wait_closed()
        wx.CallAfter(self.window.m_button_stop.SetLabel, "关闭连接")
        wx.CallAfter(self.window.m_button_start.Enable, True)
        wx.CallAfter(self.window.m_button_stop.Enable, True)
        wx.CallAfter(self.window.mesage_add, "链接已关闭")

    async def SendAll(self, json_data, mesg):
        sel = []
        bs64data = base64.b64encode(json.dumps(mesg).encode('utf-8')).decode('utf-8')
        for key, value in _cmdclass.items():
            try:
                value["object"].put(bs64data)
            except:
                sel.append(key)
        if len(sel) > 0 and json_data["Type"] == "broadcast":
            _dict = {
                "type": json_data["Type"],
                "code": 2,
                "data": {
                    "code": 10002,
                    "type": "message",
                    "mesg": sel,
                    "head": { "_id": json_data["Api"], "type": "error" }
                }
            }
            await self.websocket.send(json.dumps(_dict).encode('utf-8'))  

    async def connect(self):
        try:
            wx.CallAfter(self.window.m_button_start.Enable, False)
            wx.CallAfter(self.window.mesage_add, "[{}] 开始连接".format(self.userid))
            self.websocket = await websockets.connect("ws://" + self.ipaddr + "/api/ws?userid=" + self.userid)
            wx.CallAfter(self.window.mesage_add, "[{}] 连接成功".format(self.userid))
            while True:
                try:
                    message = await self.websocket.recv()
                    json_data = json.loads(message)
                    if json_data["Code"] == 1:
                        if json_data["Type"] in ["Addapi", "Delapi"]:
                            continue
                        send_data = json.loads(base64.b64decode(json_data["Data"]))
                        if json_data["Type"] in ["Append", "Delete", "Upfield", "Update"]:
                            if send_data["apiid"] != self.bookid: # 接收表是否当前表
                                continue
                            send_data["type"] = json_data["Type"].lower()
                        else:
                            continue
                        if self.window.statcmd:
                            await self.SendAll(json_data, send_data)
                        else:
                            wx.CallAfter(self.window.mesage_add, json.dumps(send_data))
                    elif json_data["Code"] == 3:
                        send_data = json.loads(base64.b64decode(json_data["Data"]))
                        if json_data["Type"] in ["broadcast", "retransmission"]:
                            if json_data["Type"] == "broadcast" and send_data["head"]["_id"] != self.userid: # 接收用户是否当前用户
                                continue
                            if send_data["head"]["apiid"] != self.bookid: # 判断表是否是当前绑定表
                                continue
                            send_data["head"]["root"] = send_data["head"]["_id"]
                            send_data["head"]["_id"] = json_data["Api"]
                        if self.window.statcmd:
                            await self.SendAll(json_data, send_data)
                        else:
                            wx.CallAfter(self.window.mesage_add, json.dumps(send_data))

                    elif json_data["Code"] == 202:
                        self.window.mth.my_call(call.Exe_Update)

                    elif json_data["Code"] == 203:
                        self.window.mth.my_call(async_localhost_model, self, json.loads(base64.b64decode(json_data["Data"])))

                    elif json_data["Code"] == 302:
                        if self.window.mth:
                            self.window.mth._stop()
                        self.window.mop.stop()
                        self.window.mop.join()
                        wx.CallAfter(self.window.on_restartApp, True)

                except websockets.exceptions.WebSocketException as e:
                    wx.CallAfter(self.window.mesage_add, "错误关闭： " + str(type(e)) + str(e))
                    break
                except Exception as e:
                    wx.CallAfter(self.window.mesage_add, "错误循环： " + str(type(e)) + str(e))
            await self.websocket.close()
            await self.websocket.wait_closed()
        except ConnectionRefusedError as e:
            wx.CallAfter(self.window.mesage_add, "连接失败： " + str(type(e)) + str(e))
        except Exception as e:
            wx.CallAfter(self.window.mesage_add, "连接错误： " + str(type(e)) + str(e))
        if self.window.mth.loop.is_running():
            wx.CallAfter(self.window.mesage_add, "60秒后重连")
            self.window.mth.my_call(self.window.connect_wsbsock, 30)